package idgen

import (
	"context"
	"fmt"
	log "github.com/sirupsen/logrus"
	"strconv"
	"sync"
	"time"
)

import (
	"github.com/go-redis/redis/v8"
)

var local_workid_lua = `
redis.replicate_commands();
local prefix = '_workerId:';
local workName = KEYS[1];
local workId = KEYS[2];
local workerMax = KEYS[3];
local key = prefix .. workName;
local count;
local exists = 1;
local workIdKey;

if not workerMax or workerMax == nil then
    workerMax = 1024
end

if workId == nil or workId == 0 then
    workIdKey = key .. "_" .. workId;
    workId = redis.call('GET', workIdKey);
    if workId ~= nil then
        return { workIdKey, tonumber(workId) }
    end
end

repeat
    count = tonumber(redis.call('INCR', key));
    workId = count % workerMax;
    workIdKey = key .. "_" .. workId;

    exists = tonumber(redis.call('EXISTS', workIdKey));
    if exists == 0 then
        redis.call('SET', workIdKey, workId)
    end
until exists == 0

redis.call('EXPIRE', workIdKey, 3600);
return { workIdKey, workId }
`

const (
	// 节点，指运行该生成器的系统进程，非计算机物理节点
	// 每个worker的ID位数 10位最大可以有2^10=1024个节点
	// workerID以id生成的维度为单位，享用同一个workid分配空间
	// 比如生成订单号，那么所有生成订单号的不同进程使用的workid不能一样，但在同一个进程里生成用户ID的workid可以和其他进程的订单workid一样
	WorkerBits uint8 = 10
	// 表示每个集群下的每个节点，1毫秒内可生成的id序号的二进制位数 即每毫秒可生成 2^12-1=4096个唯一ID
	SeqNumBits uint8 = 12
	// 这里求最大值使用了位运算，-1 的二进制表示为 1 的补码，感兴趣的同学可以自己算算试试 -1 ^ (-1 << nodeBits) 这里是不是等于 1023
	// 节点ID的最大值，用于防止溢出
	WorkerMax int64 = -1 ^ (-1 << WorkerBits) //nodeMax   int64 = -1 ^ (-1 << workerBits)
	// 同上，用来表示生成id序号的最大值
	SeqNumMax  int64 = -1 ^ (-1 << SeqNumBits)
	WorkerMask int64 = WorkerMax << SeqNumBits
	SeqNumMask int64 = -1 ^ (-1 << SeqNumBits)
	//// 时间戳向左的偏移量
	TimeShift uint8 = WorkerBits + SeqNumBits
	// 节点ID向左的偏移量
	WorkerShift uint8 = SeqNumBits
	// 41位字节作为时间戳数值的话 大约68年就会用完
	// 假如你2010年1月1日开始开发系统 如果不减去2010年1月1日的时间戳 那么白白浪费40年的时间戳啊！
	// 这个一旦定义且开始生成ID后千万不要改了 不然可能会生成相同的ID
	Epoch int64 = 1546300800000 //自定义的一个开始纪元时间，这样就可以将1970年开始延迟到2019年，就可以多生成49年
	//2019-01-01 00:00:00 +0000 UTC   1546300800000
	//2012-01-01 00:00:00 +0000 UTC   1325376000000
	//2018-01-01 00:00:00 +0000 UTC   1514764800000
	DefaultWorkerName = "defaultWorkerName"
)

const TIME_LAYOUT = "20060102150405.000"

type IdGeneratorFactory interface {
	Create(workerName string) (IdGenerator, error)
	ListIdGenerators() map[string]IdGenerator
	GetOrCreate(workerName string) (IdGenerator, error)
}

type IdGenerator interface {
	Next(code int) (id string)
	NextSnowId() ID
	GetWorkerId() int64
	GetWorkerName() string
	GetWorkerKey() string
}

//参考 https://developer.twitter.com/en/docs/basics/twitter-ids.html
//SnowFlake算法产生的ID是一个64位的整型，结构如下（每一部分用“-”符号分隔）：
//0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
//复制代码1位标识部分，在java中由于long的最高位是符号位，正数是0，负数是1，一般生成的ID为正数，所以为0；
//41位时间戳部分，这个是毫秒级的时间，一般实现上不会存储当前的时间戳，而是时间戳的差值（当前时间-固定的开始时间），这样可以使产生的ID从更小值开始；41位的时间戳可以使用69年，(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年；
//10位节点部分，Twitter实现中使用前5位作为数据中心标识，后5位作为机器标识，可以部署1024个节点；
//12位序列号部分，支持同一毫秒内同一个节点可以生成4096个ID；
//SnowFlake算法生成的ID大致上是按照时间递增的，用在分布式系统中时，需要注意数据中心标识和机器标识必须唯一，这样就能保证每个节点生成的ID都是唯一的。或许我们不一定都需要像上面那样使用5位作为数据中心标识，5位作为机器标识，可以根据我们业务的需要，灵活分配节点部分，如：若不需要数据中心，完全可以使用全部10位作为机器标识；若数据中心不多，也可以只使用3位作为数据中心，7位作为机器标识。
//snowflake生成的ID整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞（由datacenter和workerId作区分），并且效率较高。据说：snowflake每秒能够产生26万个ID。
//
//Twitter snowFlake 雪花算法
// | 1 bit   | 41 bit | 10 bit           | 12 bit           |
// | 保留不用 | 时间戳  | work id 最大1024  | 自增序列号 最大4096|
var fnSnow = func(code int, millisecond int64, timestamp string, workId, incrId int64) interface{} {
	id := int64((millisecond-Epoch)<<TimeShift | (workId << WorkerShift) | (incrId))
	r := ID(id)
	return r
}

var FuncCodeString0 = func(code int, millisecond int64, timestamp string, workId, incrId int64) interface{} {
	//32位
	// code:7,time:14+3,defaultWorkerId:4,incrid:4
	//0000012.20190104145814.883.0001.0001
	id := fmt.Sprintf("%.7d%s%s%.4d%.4d", code, timestamp[:14], timestamp[15:], workId, incrId)
	//log.Debug(len(id), ": ", id)
	return id
}

// time:14+3,code:7,defaultWorkerId:4,incrid:4
//20190104145814.0000012.883.0001.0001
var FuncCodeString = func(code int, millisecond int64, timestamp string, workId, incrId int64) interface{} {
	//32位
	// time:14+3,code:7,defaultWorkerId:4,incrid:4
	//20190104145814.0000012.883.0001.0001
	id := fmt.Sprintf("%s%s%.7d%.4d%.4d", timestamp[:14], timestamp[15:], code, workId, incrId)
	//log.Debug(len(id), ": ", id)
	return id
}

//32位
// code:7,time:14+3,defaultWorkerId:4,incrid:4
//0000012.20190104145814.883.0001.0001
// LocalIdGeneratorRedisFactory struct
type LocalIdGeneratorRedisFactory struct {
	client          *redis.Client
	WorkerIdMax     int64
	workerIdLuaHash string
	idGenerators    map[string]IdGenerator
	lock            *sync.Mutex
	GenFunc         func(code int, millisecond int64, timestamp string, workId, incrId int64) interface{}
}

// NewIDGenerator construct
func NewLocalIdGeneratorRedisFactory(client *redis.Client) *LocalIdGeneratorRedisFactory {
	cmd := client.ScriptLoad(context.Background(), local_workid_lua)

	if cmd.Err() != nil {
		panic(cmd.Err())
	}
	hash := cmd.Val()
	gen := &LocalIdGeneratorRedisFactory{
		client:          client,
		workerIdLuaHash: hash,
		//defaultWorkerName: DefaultWorkerName,
		WorkerIdMax:  WorkerMax,
		lock:         &sync.Mutex{},
		idGenerators: make(map[string]IdGenerator),
	}
	gen.GenFunc = FuncCodeString

	//定时刷新workId 缓存来续约
	ticker := time.NewTicker(50 * time.Minute)
	go func() {
		for {
			<-ticker.C
			for k, v := range gen.idGenerators {
				log.Infof("Renewal WorkerId：%s %v+", k, v)
				cmd := client.Get(context.Background(), v.GetWorkerKey())
				client.Expire(context.Background(), v.GetWorkerKey(), 3600)
				log.Info(cmd.Result())
			}
		}
	}()

	return gen
}

func (idGen *LocalIdGeneratorRedisFactory) GetWorkId(workerName string) int64 {
	gen, ok := idGen.idGenerators[workerName]
	if !ok {
		gen, _ = idGen.Create(workerName)
	}
	return gen.GetWorkerId()
}

func (idGen *LocalIdGeneratorRedisFactory) GetOrCreate(workerName string) (IdGenerator, error) {
	gen, ok := idGen.idGenerators[workerName]
	var err error
	if !ok {
		gen, err = idGen.Create(workerName)
	}
	return gen, err
}

func (idGen *LocalIdGeneratorRedisFactory) CreateWorkId(gen *LocalIdGenerator) (workIdKey string, workId int64, err error) {
	args := []string{gen.WorkerName, strconv.Itoa(int(gen.WorkerId)), strconv.Itoa(int(idGen.WorkerIdMax))}
	cmd := idGen.client.EvalSha(context.Background(), idGen.workerIdLuaHash, args)
	rs, err := cmd.Result()
	if err != nil {
		log.Error(err)
		return "", 0, err
	}

	vals := rs.([]interface{})

	return vals[0].(string), vals[1].(int64), err
}

func (idGen *LocalIdGeneratorRedisFactory) ListIdGenerators() map[string]IdGenerator {
	return idGen.idGenerators
}

func (idGen *LocalIdGeneratorRedisFactory) Create(workerName string) (generator IdGenerator, err error) {
	gen := NewLocalIdGenerator(0, workerName)
	gen.GenFunc = idGen.GenFunc
	idKey, workerId, err := idGen.CreateWorkId(gen)
	if err != nil {
		return nil, err
	}
	gen.WorkerIdKey = idKey
	gen.WorkerId = workerId
	idGen.idGenerators[workerName] = gen
	log.Infof("generate WorkerId: %s = %d", idKey, workerId)
	return gen, err
}
